qnty 0.0.1__py3-none-any.whl → 0.0.2__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 +3 -0
- qnty/dimension.py +5 -4
- qnty/equation.py +216 -0
- qnty/expression.py +480 -0
- qnty/unit.py +8 -8
- qnty/units.py +2 -3
- qnty/variable.py +68 -29
- qnty/variables.py +217 -19
- {qnty-0.0.1.dist-info → qnty-0.0.2.dist-info}/METADATA +109 -22
- qnty-0.0.2.dist-info/RECORD +11 -0
- qnty/setters.py +0 -89
- qnty-0.0.1.dist-info/RECORD +0 -10
- {qnty-0.0.1.dist-info → qnty-0.0.2.dist-info}/WHEEL +0 -0
qnty/variable.py
CHANGED
@@ -2,21 +2,36 @@
|
|
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, ENERGY, FORCE, LENGTH, PRESSURE, VOLUME, DimensionSignature
|
11
14
|
from .unit import UnitConstant, UnitDefinition, registry
|
12
15
|
from .units import DimensionlessUnits, LengthUnits, PressureUnits
|
13
16
|
|
14
|
-
|
15
|
-
|
17
|
+
# TypeVar for generic dimensional types
|
18
|
+
DimensionType = TypeVar('DimensionType', bound='FastQuantity')
|
16
19
|
|
17
20
|
|
18
|
-
|
19
|
-
|
21
|
+
class TypeSafeSetter:
|
22
|
+
"""Basic type-safe setter that accepts compatible units."""
|
23
|
+
|
24
|
+
def __init__(self, variable: TypeSafeVariable, value: float):
|
25
|
+
self.variable = variable
|
26
|
+
self.value = value
|
27
|
+
|
28
|
+
def with_unit(self, unit: UnitConstant) -> TypeSafeVariable:
|
29
|
+
"""Set with type-safe unit constant."""
|
30
|
+
if not self.variable.expected_dimension.is_compatible(unit.dimension):
|
31
|
+
raise TypeError(f"Unit {unit.name} incompatible with expected dimension")
|
32
|
+
|
33
|
+
self.variable.quantity = FastQuantity(self.value, unit)
|
34
|
+
return self.variable
|
20
35
|
|
21
36
|
|
22
37
|
class FastQuantity:
|
@@ -39,7 +54,7 @@ class FastQuantity:
|
|
39
54
|
return f"FastQuantity({self.value}, {self.unit.name})"
|
40
55
|
|
41
56
|
# Ultra-fast arithmetic with dimensional checking
|
42
|
-
def __add__(self, other:
|
57
|
+
def __add__(self, other: FastQuantity) -> FastQuantity:
|
43
58
|
# Fast dimension compatibility check using cached signatures
|
44
59
|
if self._dimension_sig != other._dimension_sig:
|
45
60
|
raise ValueError(f"Cannot add {self.unit.name} and {other.unit.name}")
|
@@ -52,7 +67,7 @@ class FastQuantity:
|
|
52
67
|
other_value = other.value * other._si_factor / self._si_factor
|
53
68
|
return FastQuantity(self.value + other_value, self.unit)
|
54
69
|
|
55
|
-
def __sub__(self, other:
|
70
|
+
def __sub__(self, other: FastQuantity) -> FastQuantity:
|
56
71
|
# Fast dimension compatibility check using cached signatures
|
57
72
|
if self._dimension_sig != other._dimension_sig:
|
58
73
|
raise ValueError(f"Cannot subtract {other.unit.name} from {self.unit.name}")
|
@@ -65,8 +80,8 @@ class FastQuantity:
|
|
65
80
|
other_value = other.value * other._si_factor / self._si_factor
|
66
81
|
return FastQuantity(self.value - other_value, self.unit)
|
67
82
|
|
68
|
-
def __mul__(self, other:
|
69
|
-
if isinstance(other,
|
83
|
+
def __mul__(self, other: FastQuantity | float | int) -> FastQuantity:
|
84
|
+
if isinstance(other, int | float):
|
70
85
|
return FastQuantity(self.value * other, self.unit)
|
71
86
|
|
72
87
|
# Fast dimensional analysis using cached signatures
|
@@ -83,14 +98,14 @@ class FastQuantity:
|
|
83
98
|
|
84
99
|
return FastQuantity(result_value, result_unit)
|
85
100
|
|
86
|
-
def __rmul__(self, other:
|
101
|
+
def __rmul__(self, other: float | int) -> FastQuantity:
|
87
102
|
"""Reverse multiplication for cases like 2 * quantity."""
|
88
|
-
if isinstance(other,
|
103
|
+
if isinstance(other, int | float):
|
89
104
|
return FastQuantity(other * self.value, self.unit)
|
90
105
|
return NotImplemented
|
91
106
|
|
92
|
-
def __truediv__(self, other:
|
93
|
-
if isinstance(other,
|
107
|
+
def __truediv__(self, other: FastQuantity | float | int) -> FastQuantity:
|
108
|
+
if isinstance(other, int | float):
|
94
109
|
return FastQuantity(self.value / other, self.unit)
|
95
110
|
|
96
111
|
# Fast dimensional analysis using cached signatures
|
@@ -107,8 +122,8 @@ class FastQuantity:
|
|
107
122
|
|
108
123
|
return FastQuantity(result_value, result_unit)
|
109
124
|
|
110
|
-
def _find_result_unit_fast(self, result_dimension_sig: int,
|
111
|
-
left_qty:
|
125
|
+
def _find_result_unit_fast(self, result_dimension_sig: int,
|
126
|
+
left_qty: FastQuantity, right_qty: FastQuantity) -> UnitConstant:
|
112
127
|
"""Ultra-fast unit finding using cached dimension signatures."""
|
113
128
|
|
114
129
|
# Initialize dimension cache if empty
|
@@ -130,7 +145,7 @@ class FastQuantity:
|
|
130
145
|
# For rare combined dimensions, create temporary unit
|
131
146
|
temp_unit = UnitDefinition(
|
132
147
|
name=f"combined_{result_dimension_sig}",
|
133
|
-
symbol="combined",
|
148
|
+
symbol="combined",
|
134
149
|
dimension=DimensionSignature(result_dimension_sig),
|
135
150
|
si_factor=1.0
|
136
151
|
)
|
@@ -140,13 +155,13 @@ class FastQuantity:
|
|
140
155
|
registry._dimension_cache[result_dimension_sig] = result_unit
|
141
156
|
return result_unit
|
142
157
|
|
143
|
-
def _find_result_unit(self, result_dimension: DimensionSignature,
|
144
|
-
left_qty:
|
158
|
+
def _find_result_unit(self, result_dimension: DimensionSignature,
|
159
|
+
left_qty: FastQuantity, right_qty: FastQuantity) -> UnitConstant:
|
145
160
|
"""Legacy method - kept for compatibility."""
|
146
161
|
return self._find_result_unit_fast(result_dimension._signature, left_qty, right_qty)
|
147
162
|
|
148
163
|
# Ultra-fast comparisons
|
149
|
-
def __lt__(self, other:
|
164
|
+
def __lt__(self, other: FastQuantity) -> bool:
|
150
165
|
if self._dimension_sig != other._dimension_sig:
|
151
166
|
raise ValueError("Cannot compare incompatible dimensions")
|
152
167
|
|
@@ -172,7 +187,7 @@ class FastQuantity:
|
|
172
187
|
other_value = other.value * other._si_factor / self._si_factor
|
173
188
|
return abs(self.value - other_value) < 1e-10
|
174
189
|
|
175
|
-
def to(self, target_unit: UnitConstant) ->
|
190
|
+
def to(self, target_unit: UnitConstant) -> FastQuantity:
|
176
191
|
"""Ultra-fast unit conversion."""
|
177
192
|
if self.unit == target_unit:
|
178
193
|
return FastQuantity(self.value, target_unit)
|
@@ -183,16 +198,40 @@ class FastQuantity:
|
|
183
198
|
|
184
199
|
|
185
200
|
class TypeSafeVariable(Generic[DimensionType]):
|
186
|
-
"""
|
201
|
+
"""
|
202
|
+
Base class for type-safe variables with dimensional checking.
|
187
203
|
|
188
|
-
|
204
|
+
This is a simple data container without dependencies on expressions or equations.
|
205
|
+
Mathematical operations are added by subclasses or mixins.
|
206
|
+
"""
|
207
|
+
|
208
|
+
# Class attribute defining which setter to use - subclasses can override
|
209
|
+
_setter_class = TypeSafeSetter
|
210
|
+
|
211
|
+
def __init__(self, name: str, expected_dimension, is_known: bool = True):
|
189
212
|
self.name = name
|
213
|
+
self.symbol: str | None = None # Will be set by EngineeringProblem to attribute name
|
190
214
|
self.expected_dimension = expected_dimension
|
191
|
-
self.quantity:
|
215
|
+
self.quantity: FastQuantity | None = None
|
216
|
+
self.is_known = is_known
|
217
|
+
|
218
|
+
def set(self, value: float):
|
219
|
+
"""Create a setter for this variable using the class-specific setter type."""
|
220
|
+
return self._setter_class(self, value)
|
192
221
|
|
193
|
-
|
194
|
-
|
195
|
-
|
222
|
+
@property
|
223
|
+
def unknown(self) -> Self:
|
224
|
+
"""Mark this variable as unknown using fluent API."""
|
225
|
+
self.is_known = False
|
226
|
+
return self
|
227
|
+
|
228
|
+
@property
|
229
|
+
def known(self) -> Self:
|
230
|
+
"""Mark this variable as known using fluent API."""
|
231
|
+
self.is_known = True
|
232
|
+
return self
|
196
233
|
|
197
234
|
def __str__(self):
|
198
|
-
return f"{self.name}: {self.quantity}" if self.quantity else f"{self.name}: unset"
|
235
|
+
return f"{self.name}: {self.quantity}" if self.quantity else f"{self.name}: unset"
|
236
|
+
|
237
|
+
|
qnty/variables.py
CHANGED
@@ -1,31 +1,229 @@
|
|
1
1
|
"""
|
2
|
-
Specialized Variables
|
3
|
-
|
2
|
+
Specialized Variables and Setters
|
3
|
+
==================================
|
4
4
|
|
5
|
-
Dimension-specific variable classes with type safety
|
5
|
+
Dimension-specific variable classes with type safety, fluent API setters,
|
6
|
+
and mathematical operations for expressions and equations.
|
6
7
|
"""
|
7
8
|
|
8
|
-
from
|
9
|
-
from .variable import TypeSafeVariable
|
10
|
-
from .setters import LengthSetter, PressureSetter
|
9
|
+
from __future__ import annotations
|
11
10
|
|
11
|
+
from typing import cast
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
from .dimension import DIMENSIONLESS, LENGTH, PRESSURE
|
14
|
+
from .equation import Equation
|
15
|
+
from .expression import Expression, wrap_operand
|
16
|
+
from .units import DimensionlessUnits, LengthUnits, PressureUnits
|
17
|
+
from .variable import FastQuantity, TypeSafeSetter, TypeSafeVariable
|
18
|
+
|
19
|
+
|
20
|
+
class ExpressionVariable(TypeSafeVariable):
|
21
|
+
"""
|
22
|
+
TypeSafeVariable extended with expression and equation capabilities.
|
23
|
+
|
24
|
+
This adds mathematical operations that create expressions and equations,
|
25
|
+
keeping the base TypeSafeVariable free of these dependencies.
|
26
|
+
"""
|
27
|
+
|
28
|
+
def equals(self, expression: Expression | TypeSafeVariable | FastQuantity | int | float):
|
29
|
+
"""Create an equation: self = expression."""
|
30
|
+
# Wrap the expression in proper Expression type
|
31
|
+
rhs_expr = wrap_operand(expression)
|
32
|
+
return Equation(f"{self.name}_eq", self, rhs_expr)
|
33
|
+
|
34
|
+
def __add__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
35
|
+
"""Add this variable to another operand, returning an Expression."""
|
36
|
+
return wrap_operand(self) + wrap_operand(other)
|
37
|
+
|
38
|
+
def __radd__(self, other: FastQuantity | int | float) -> Expression:
|
39
|
+
"""Reverse add for this variable."""
|
40
|
+
return wrap_operand(other) + wrap_operand(self)
|
41
|
+
|
42
|
+
def __sub__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
43
|
+
"""Subtract another operand from this variable, returning an Expression."""
|
44
|
+
return wrap_operand(self) - wrap_operand(other)
|
45
|
+
|
46
|
+
def __rsub__(self, other: FastQuantity | int | float) -> Expression:
|
47
|
+
"""Reverse subtract for this variable."""
|
48
|
+
return wrap_operand(other) - wrap_operand(self)
|
49
|
+
|
50
|
+
def __mul__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
51
|
+
"""Multiply this variable by another operand, returning an Expression."""
|
52
|
+
return wrap_operand(self) * wrap_operand(other)
|
53
|
+
|
54
|
+
def __rmul__(self, other: FastQuantity | int | float) -> Expression:
|
55
|
+
"""Reverse multiply for this variable."""
|
56
|
+
return wrap_operand(other) * wrap_operand(self)
|
16
57
|
|
17
|
-
def
|
18
|
-
|
58
|
+
def __truediv__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
59
|
+
"""Divide this variable by another operand, returning an Expression."""
|
60
|
+
return wrap_operand(self) / wrap_operand(other)
|
61
|
+
|
62
|
+
def __rtruediv__(self, other: FastQuantity | int | float) -> Expression:
|
63
|
+
"""Reverse divide for this variable."""
|
64
|
+
return wrap_operand(other) / wrap_operand(self)
|
65
|
+
|
66
|
+
def __pow__(self, other: TypeSafeVariable | FastQuantity | int | float) -> Expression:
|
67
|
+
"""Raise this variable to a power, returning an Expression."""
|
68
|
+
return wrap_operand(self) ** wrap_operand(other)
|
69
|
+
|
70
|
+
def __rpow__(self, other: FastQuantity | int | float) -> Expression:
|
71
|
+
"""Reverse power for this variable."""
|
72
|
+
return wrap_operand(other) ** wrap_operand(self)
|
73
|
+
|
74
|
+
|
75
|
+
# Specialized setter classes
|
76
|
+
class LengthSetter(TypeSafeSetter):
|
77
|
+
"""Length-specific setter with only length units."""
|
19
78
|
|
20
|
-
def
|
21
|
-
|
79
|
+
def __init__(self, variable: Length, value: float):
|
80
|
+
super().__init__(variable, value)
|
81
|
+
|
82
|
+
# Only length units available - compile-time safe!
|
83
|
+
@property
|
84
|
+
def meters(self) -> Length:
|
85
|
+
self.variable.quantity = FastQuantity(self.value, LengthUnits.meter)
|
86
|
+
return cast(Length, self.variable)
|
87
|
+
|
88
|
+
@property
|
89
|
+
def millimeters(self) -> Length:
|
90
|
+
self.variable.quantity = FastQuantity(self.value, LengthUnits.millimeter)
|
91
|
+
return cast(Length, self.variable)
|
92
|
+
|
93
|
+
@property
|
94
|
+
def inches(self) -> Length:
|
95
|
+
self.variable.quantity = FastQuantity(self.value, LengthUnits.inch)
|
96
|
+
return cast(Length, self.variable)
|
97
|
+
|
98
|
+
@property
|
99
|
+
def feet(self) -> Length:
|
100
|
+
self.variable.quantity = FastQuantity(self.value, LengthUnits.foot)
|
101
|
+
return cast(Length, self.variable)
|
22
102
|
|
23
103
|
|
24
|
-
class
|
25
|
-
"""
|
104
|
+
class PressureSetter(TypeSafeSetter):
|
105
|
+
"""Pressure-specific setter with only pressure units."""
|
106
|
+
|
107
|
+
def __init__(self, variable: Pressure, value: float):
|
108
|
+
super().__init__(variable, value)
|
26
109
|
|
27
|
-
|
28
|
-
|
110
|
+
# Only pressure units available - compile-time safe!
|
111
|
+
@property
|
112
|
+
def psi(self) -> Pressure:
|
113
|
+
self.variable.quantity = FastQuantity(self.value, PressureUnits.psi)
|
114
|
+
return cast(Pressure, self.variable)
|
29
115
|
|
30
|
-
|
31
|
-
|
116
|
+
@property
|
117
|
+
def kPa(self) -> Pressure:
|
118
|
+
self.variable.quantity = FastQuantity(self.value, PressureUnits.kilopascal)
|
119
|
+
return cast(Pressure, self.variable)
|
120
|
+
|
121
|
+
@property
|
122
|
+
def MPa(self) -> Pressure:
|
123
|
+
self.variable.quantity = FastQuantity(self.value, PressureUnits.megapascal)
|
124
|
+
return cast(Pressure, self.variable)
|
125
|
+
|
126
|
+
@property
|
127
|
+
def bar(self) -> Pressure:
|
128
|
+
self.variable.quantity = FastQuantity(self.value, PressureUnits.bar)
|
129
|
+
return cast(Pressure, self.variable)
|
130
|
+
|
131
|
+
|
132
|
+
class DimensionlessSetter(TypeSafeSetter):
|
133
|
+
"""Dimensionless-specific setter with only dimensionless units."""
|
134
|
+
|
135
|
+
def __init__(self, variable: Dimensionless, value: float):
|
136
|
+
super().__init__(variable, value)
|
137
|
+
|
138
|
+
# Dimensionless units
|
139
|
+
@property
|
140
|
+
def dimensionless(self) -> Dimensionless:
|
141
|
+
self.variable.quantity = FastQuantity(self.value, DimensionlessUnits.dimensionless)
|
142
|
+
return cast(Dimensionless, self.variable)
|
143
|
+
|
144
|
+
# Common alias for no units
|
145
|
+
@property
|
146
|
+
def unitless(self) -> Dimensionless:
|
147
|
+
self.variable.quantity = FastQuantity(self.value, DimensionlessUnits.dimensionless)
|
148
|
+
return cast(Dimensionless, self.variable)
|
149
|
+
|
150
|
+
|
151
|
+
# Specialized variable types that users interact with
|
152
|
+
class Length(ExpressionVariable):
|
153
|
+
"""Type-safe length variable with expression capabilities."""
|
154
|
+
|
155
|
+
_setter_class = LengthSetter
|
156
|
+
|
157
|
+
def __init__(self, *args, is_known: bool = True):
|
158
|
+
if len(args) == 1:
|
159
|
+
# Length("name") - original syntax
|
160
|
+
super().__init__(args[0], LENGTH, is_known=is_known)
|
161
|
+
elif len(args) == 3:
|
162
|
+
# Length(value, "unit", "name") - new syntax
|
163
|
+
value, unit, name = args
|
164
|
+
super().__init__(name, LENGTH, is_known=is_known)
|
165
|
+
# Auto-set the value with the specified unit
|
166
|
+
setter = LengthSetter(self, value)
|
167
|
+
# Get the unit setter method dynamically
|
168
|
+
if unit == "in": # Handle "in" alias for inches
|
169
|
+
setter.inches
|
170
|
+
elif hasattr(setter, unit):
|
171
|
+
getattr(setter, unit)
|
172
|
+
elif hasattr(setter, unit + 's'): # Handle singular/plural
|
173
|
+
getattr(setter, unit + 's')
|
174
|
+
else:
|
175
|
+
# Default to meters if unit not recognized
|
176
|
+
setter.meters
|
177
|
+
else:
|
178
|
+
raise ValueError("Length expects either 1 argument (name) or 3 arguments (value, unit, name)")
|
179
|
+
|
180
|
+
|
181
|
+
class Pressure(ExpressionVariable):
|
182
|
+
"""Type-safe pressure variable with expression capabilities."""
|
183
|
+
|
184
|
+
_setter_class = PressureSetter
|
185
|
+
|
186
|
+
def __init__(self, *args, is_known: bool = True):
|
187
|
+
if len(args) == 1:
|
188
|
+
# Pressure("name") - original syntax
|
189
|
+
super().__init__(args[0], PRESSURE, is_known=is_known)
|
190
|
+
elif len(args) == 3:
|
191
|
+
# Pressure(value, "unit", "name") - new syntax
|
192
|
+
value, unit, name = args
|
193
|
+
super().__init__(name, PRESSURE, is_known=is_known)
|
194
|
+
# Auto-set the value with the specified unit
|
195
|
+
setter = PressureSetter(self, value)
|
196
|
+
# Get the unit setter method dynamically
|
197
|
+
if unit == "psi":
|
198
|
+
setter.psi
|
199
|
+
elif unit == "kPa":
|
200
|
+
setter.kPa
|
201
|
+
elif unit == "MPa":
|
202
|
+
setter.MPa
|
203
|
+
elif unit == "bar":
|
204
|
+
setter.bar
|
205
|
+
else:
|
206
|
+
# Default to psi if unit not recognized
|
207
|
+
setter.psi
|
208
|
+
else:
|
209
|
+
raise ValueError("Pressure expects either 1 argument (name) or 3 arguments (value, unit, name)")
|
210
|
+
|
211
|
+
|
212
|
+
class Dimensionless(ExpressionVariable):
|
213
|
+
"""Type-safe dimensionless variable with expression capabilities."""
|
214
|
+
|
215
|
+
_setter_class = DimensionlessSetter
|
216
|
+
|
217
|
+
def __init__(self, *args, is_known: bool = True):
|
218
|
+
if len(args) == 1:
|
219
|
+
# Dimensionless("name") - original syntax
|
220
|
+
super().__init__(args[0], DIMENSIONLESS, is_known=is_known)
|
221
|
+
elif len(args) == 2:
|
222
|
+
# Dimensionless(value, "name") - new syntax
|
223
|
+
value, name = args
|
224
|
+
super().__init__(name, DIMENSIONLESS, is_known=is_known)
|
225
|
+
# Auto-set the value as dimensionless
|
226
|
+
setter = DimensionlessSetter(self, value)
|
227
|
+
setter.dimensionless
|
228
|
+
else:
|
229
|
+
raise ValueError("Dimensionless expects either 1 argument (name) or 2 arguments (value, name)")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: qnty
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.2
|
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
|
@@ -33,7 +33,7 @@ Description-Content-Type: text/markdown
|
|
33
33
|
|
34
34
|
**High-performance unit system library for Python with dimensional safety and fast unit conversions for engineering calculations.**
|
35
35
|
|
36
|
-
[](https://www.python.org/downloads/)
|
37
37
|
[](https://opensource.org/licenses/Apache-2.0)
|
38
38
|
[](https://pypi.org/project/qnty/)
|
39
39
|
|
@@ -43,7 +43,7 @@ Description-Content-Type: text/markdown
|
|
43
43
|
|
44
44
|
**📐 Accuracy Notice**: The authors are not responsible or liable for incorrect results, calculation errors, or any consequences arising from the use of this library. Always validate calculations independently using established engineering tools and practices.
|
45
45
|
|
46
|
-
**🚀 Learn from History**: Remember, even NASA's Mars Climate Orbiter had a $327 million oops moment due to unit conversion errors between metric and imperial systems. Don't let your project become the next cautionary tale - double-check everything!
|
46
|
+
**🚀 Learn from History**: Remember, even NASA's Mars Climate Orbiter had a $327 million oops moment due to unit conversion errors between metric and imperial systems. Don't let your project become the next cautionary tale - double-check everything!
|
47
47
|
|
48
48
|
*Use Qnty to help prevent unit errors, but always verify critical calculations through multiple methods.*
|
49
49
|
|
@@ -58,7 +58,9 @@ Qnty is designed around **type safety** and **performance optimization** using c
|
|
58
58
|
- **⚡ Zero-Cost Abstractions**: Optimized operations with `__slots__` and caching
|
59
59
|
- **🔗 Fluent API**: Intuitive method chaining for readable code
|
60
60
|
- **🧮 Engineering-Focused**: Built for real-world engineering calculations
|
61
|
-
-
|
61
|
+
- **🧬 Mathematical System**: Built-in equation solving and expression trees
|
62
|
+
- **📊 Comprehensive Testing**: 457 tests with performance benchmarks
|
63
|
+
- **🏗️ Clean Architecture**: Circular import-free design with strict dependency hierarchy
|
62
64
|
|
63
65
|
## 🚀 Quick Start
|
64
66
|
|
@@ -73,7 +75,7 @@ poetry add qnty
|
|
73
75
|
### Basic Usage
|
74
76
|
|
75
77
|
```python
|
76
|
-
from qnty
|
78
|
+
from qnty import Length, Pressure, Dimensionless
|
77
79
|
from qnty.variable import FastQuantity
|
78
80
|
from qnty.units import LengthUnits, PressureUnits
|
79
81
|
|
@@ -95,7 +97,7 @@ force = pressure * area # Automatic dimensional analysis
|
|
95
97
|
### Engineering Example
|
96
98
|
|
97
99
|
```python
|
98
|
-
from qnty
|
100
|
+
from qnty import Length, Pressure
|
99
101
|
|
100
102
|
# ASME pressure vessel calculation with mixed units
|
101
103
|
pressure = Pressure("internal_pressure")
|
@@ -112,47 +114,90 @@ thickness = (pressure.quantity * diameter.quantity) / (2 * stress.quantity)
|
|
112
114
|
print(f"Required thickness: {thickness}") # Automatically in correct units
|
113
115
|
```
|
114
116
|
|
117
|
+
### Mathematical Equations & Solving
|
118
|
+
|
119
|
+
```python
|
120
|
+
from qnty import Length, Pressure, Dimensionless
|
121
|
+
|
122
|
+
# Define engineering variables
|
123
|
+
T = Length("Wall Thickness", is_known=False) # Unknown to solve for
|
124
|
+
T_bar = Length(0.147, "inches", "Nominal Wall Thickness")
|
125
|
+
U_m = Dimensionless(0.125, "Mill Undertolerance")
|
126
|
+
|
127
|
+
# Create equation using fluent API: T = T_bar * (1 - U_m)
|
128
|
+
equation = T.equals(T_bar * (1 - U_m))
|
129
|
+
|
130
|
+
# Solve automatically
|
131
|
+
known_vars = {"T_bar": T_bar, "U_m": U_m}
|
132
|
+
result = equation.solve_for("T", known_vars)
|
133
|
+
print(f"Solved thickness: {result.quantity}") # 0.128625 inches
|
134
|
+
|
135
|
+
# Verify equation is satisfied
|
136
|
+
assert equation.check_residual(known_vars) is True
|
137
|
+
```
|
138
|
+
|
115
139
|
## 🏗️ Architecture
|
116
140
|
|
141
|
+
### Clean Dependency Design
|
142
|
+
|
143
|
+
Qnty features a carefully designed architecture that eliminates circular imports through a strict dependency hierarchy:
|
144
|
+
|
145
|
+
```python
|
146
|
+
variable → variables → expression → equation
|
147
|
+
```
|
148
|
+
|
149
|
+
This ensures clean type checking, maintainable code, and optimal performance throughout the system.
|
150
|
+
|
117
151
|
### Core Components
|
118
152
|
|
119
|
-
|
153
|
+
### 🔢 Dimensional System
|
154
|
+
|
120
155
|
- Prime number encoding for ultra-fast dimensional compatibility checks
|
121
156
|
- Zero-cost dimensional analysis at compile time
|
122
157
|
- Immutable dimension signatures for thread safety
|
123
158
|
|
124
|
-
|
159
|
+
### ⚙️ High-Performance Quantities
|
160
|
+
|
125
161
|
- `FastQuantity`: Optimized for engineering calculations with `__slots__`
|
126
162
|
- Cached SI factors and dimension signatures
|
127
163
|
- Fast-path optimizations for same-unit operations
|
128
164
|
|
129
|
-
|
130
|
-
|
165
|
+
### 🎯 Type-Safe Variables
|
166
|
+
|
167
|
+
- `Length`, `Pressure`, `Dimensionless`: Domain-specific variables with compile-time safety
|
131
168
|
- Fluent API with specialized setters
|
132
169
|
- Prevents dimensional errors at the type level
|
133
170
|
|
134
|
-
|
171
|
+
### 🔄 Smart Unit System
|
172
|
+
|
135
173
|
- Pre-computed conversion tables
|
136
174
|
- Automatic unit resolution for calculations
|
137
175
|
- Support for mixed-unit operations
|
138
176
|
|
177
|
+
### 🧬 Mathematical System
|
178
|
+
|
179
|
+
- Built-in equation solving with symbolic manipulation
|
180
|
+
- Expression trees for complex mathematical operations
|
181
|
+
- Automatic residual checking and validation
|
182
|
+
- Engineering equation support (ASME, pressure vessels, etc.)
|
183
|
+
|
139
184
|
## 📊 Performance
|
140
185
|
|
141
|
-
Qnty significantly outperforms other unit libraries with **
|
186
|
+
Qnty significantly outperforms other unit libraries with **18.9x average speedup** over Pint:
|
142
187
|
|
143
188
|
### Real Benchmark Results (μs per operation)
|
144
189
|
|
145
190
|
| Operation | Qnty | Pint | **Speedup** |
|
146
191
|
|-----------|------|------|-------------|
|
147
|
-
| Unit Conversion (m → mm) | 0.
|
148
|
-
| Mixed Unit Addition (mm + in) |
|
149
|
-
| Multiplication (m × m) | 0.
|
150
|
-
| Division (psi ÷ mm) |
|
151
|
-
| Complex ASME Equation |
|
152
|
-
| Type-Safe Variables |
|
153
|
-
| Chained Operations |
|
154
|
-
| Loop (10 additions) |
|
155
|
-
| **AVERAGE** | **
|
192
|
+
| Unit Conversion (m → mm) | 0.50 | 9.72 | **19.5x** |
|
193
|
+
| Mixed Unit Addition (mm + in) | 0.76 | 17.52 | **23.1x** |
|
194
|
+
| Multiplication (m × m) | 0.82 | 10.64 | **12.9x** |
|
195
|
+
| Division (psi ÷ mm) | 0.87 | 11.23 | **12.9x** |
|
196
|
+
| Complex ASME Equation | 4.07 | 106.17 | **26.1x** 🚀 |
|
197
|
+
| Type-Safe Variables | 0.98 | 9.65 | **9.8x** |
|
198
|
+
| Chained Operations | 1.83 | 42.22 | **23.1x** |
|
199
|
+
| Loop (10 additions) | 5.32 | 79.48 | **14.9x** |
|
200
|
+
| **AVERAGE** | **1.89** | **35.83** | **18.9x** 🏆 |
|
156
201
|
|
157
202
|
*Benchmarks performed on typical engineering calculations. Run `pytest tests/test_benchmark.py -v -s` to verify on your system.*
|
158
203
|
|
@@ -197,6 +242,44 @@ area = width.quantity * height.quantity
|
|
197
242
|
perimeter = 2 * (width.quantity + height.quantity)
|
198
243
|
```
|
199
244
|
|
245
|
+
### Equation Solving System
|
246
|
+
|
247
|
+
```python
|
248
|
+
from qnty import Length, Pressure, Dimensionless
|
249
|
+
|
250
|
+
# Multi-variable engineering equations
|
251
|
+
P = Pressure(90, "psi", "P") # Known
|
252
|
+
D = Length(0.84, "inches", "D") # Known
|
253
|
+
t = Length("t", is_known=False) # Unknown - solve for this
|
254
|
+
S = Pressure(20000, "psi", "S") # Known
|
255
|
+
|
256
|
+
# ASME pressure vessel equation: P = (S * t) / ((D/2) + 0.6*t)
|
257
|
+
# Rearranged to solve for t
|
258
|
+
equation = t.equals((P * D) / (2 * S - 1.2 * P))
|
259
|
+
|
260
|
+
# Solve automatically
|
261
|
+
known_variables = {"P": P, "D": D, "S": S}
|
262
|
+
thickness_result = equation.solve_for("t", known_variables)
|
263
|
+
print(f"Required thickness: {thickness_result.quantity}")
|
264
|
+
|
265
|
+
# Verify solution
|
266
|
+
assert equation.check_residual(known_variables) is True
|
267
|
+
```
|
268
|
+
|
269
|
+
### Import Strategy
|
270
|
+
|
271
|
+
Qnty provides a clean, minimal public API:
|
272
|
+
|
273
|
+
```python
|
274
|
+
# Preferred import style - clean public API
|
275
|
+
from qnty import Length, Pressure, Dimensionless
|
276
|
+
|
277
|
+
# Internal imports when needed for advanced usage
|
278
|
+
from qnty.variable import FastQuantity, TypeSafeVariable
|
279
|
+
from qnty.expression import Expression
|
280
|
+
from qnty.equation import Equation, EquationSystem
|
281
|
+
```
|
282
|
+
|
200
283
|
## 🔧 Development
|
201
284
|
|
202
285
|
### Setup Development Environment
|
@@ -221,6 +304,7 @@ python tests/test_benchmark.py
|
|
221
304
|
```bash
|
222
305
|
# Linting with ruff (200 character line length)
|
223
306
|
ruff check src/ tests/
|
307
|
+
ruff format src/ tests/
|
224
308
|
|
225
309
|
# Type checking
|
226
310
|
mypy src/qnty/
|
@@ -232,7 +316,9 @@ mypy src/qnty/
|
|
232
316
|
|
233
317
|
- **`FastQuantity`**: High-performance quantity with value and unit
|
234
318
|
- **`TypeSafeVariable`**: Base class for dimension-specific variables
|
235
|
-
- **`Length`**, **`Pressure`**: Specialized variables with fluent setters
|
319
|
+
- **`Length`**, **`Pressure`**, **`Dimensionless`**: Specialized variables with fluent setters
|
320
|
+
- **`Equation`**: Mathematical equations with solving capabilities
|
321
|
+
- **`Expression`**: Abstract base for mathematical expression trees
|
236
322
|
- **`DimensionSignature`**: Immutable dimension encoding system
|
237
323
|
- **`UnitConstant`**: Type-safe unit definitions
|
238
324
|
|
@@ -266,3 +352,4 @@ This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENS
|
|
266
352
|
---
|
267
353
|
|
268
354
|
**Ready to supercharge your engineering calculations?** Install Qnty today and experience the power of type-safe, high-performance unit handling! 🚀
|
355
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
qnty/__init__.py,sha256=wzFXh3bgSbjHzg1tHYRwEcEO25mijhsdohHUQDibVu4,106
|
2
|
+
qnty/dimension.py,sha256=X9v4_-8IhLcUsXzYgC6rXaGZ-Y6tqB8MPZgN9re_lx8,2781
|
3
|
+
qnty/equation.py,sha256=XgzM91jn__GomKbgslncoFJWPDJiMb4_VKD-8sdcFXc,9096
|
4
|
+
qnty/expression.py,sha256=UM0hCqOVD1gK_O5-Rsq7BWIPDTEL_kFORprV6LIqGcU,20890
|
5
|
+
qnty/unit.py,sha256=dgJX6rcbeAv6NFyQtrT-Zp7m93X39GlU9jkcRYBKic4,4187
|
6
|
+
qnty/units.py,sha256=uazbYoC3sFE6MKd46f2BrysmgJ-r0v7BvKGsvw8a2W0,1367
|
7
|
+
qnty/variable.py,sha256=6Z-QmLE3ke2fdw-d6hNR7OSC7aAhE02htHY5XTN7zgI,9808
|
8
|
+
qnty/variables.py,sha256=_dchFWFOedWkGDMC4AhZ_kHEM6VVH7iqANksVyfHoLc,9039
|
9
|
+
qnty-0.0.2.dist-info/METADATA,sha256=90UoHneLF5m66UFkdjV84C5KohJVBxMy0wzx1LN8rD0,12113
|
10
|
+
qnty-0.0.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
11
|
+
qnty-0.0.2.dist-info/RECORD,,
|